using System;
using LegoBotPrima.Actions;
using LegoBotPrimaAbstractions;
using Moq;
using NUnit.Framework;

namespace LegoBotPrimaTests
{
    [TestFixture]
    public class GoToAngleBotAction_Fixture : BaseFixture
    {
        [Test]
        public void Can_Go_From_250_To_85()
        {
            var state = new GoToAngleTestState { CurrentAngle = 250, TargetAngle = 85 };
            var action = PrepareGoToAngleBotAction(state);

            AssertDirection(state, Dir.Left);

            SimulateSpinning(state, action);

            AssertEnd(state);
        }

        [Test]
        public void Can_Go_From_270_To_85()
        {
            var state = new GoToAngleTestState { CurrentAngle = 270, TargetAngle = 85 };
            var action = PrepareGoToAngleBotAction(state);

            AssertDirection(state, Dir.Right);

            SimulateSpinning(state, action);

            AssertEnd(state);
        }

        [Test]
        public void Can_Go_From_85_To_250()
        {
            var state = new GoToAngleTestState { CurrentAngle = 85, TargetAngle = 250 };
            var action = PrepareGoToAngleBotAction(state);

            AssertDirection(state, Dir.Right);

            SimulateSpinning(state, action);

            AssertEnd(state);
        }

        [Test]
        public void Can_Go_From_85_To_270()
        {
            var state = new GoToAngleTestState { CurrentAngle = 85, TargetAngle = 270 };
            var action = PrepareGoToAngleBotAction(state);

            AssertDirection(state, Dir.Left);

            SimulateSpinning(state, action);

            AssertEnd(state);
        }


        [Test]
        public void Can_Go_From_20_To_50()
        {
            var state = new GoToAngleTestState { CurrentAngle = 20, TargetAngle = 50 };
            var action = PrepareGoToAngleBotAction(state);

            AssertDirection(state, Dir.Right);

            SimulateSpinning(state, action);

            AssertEnd(state);
        }

        [Test]
        public void Can_Go_From_50_To_20()
        {
            var state = new GoToAngleTestState { CurrentAngle = 50, TargetAngle = 20 };
            var action = PrepareGoToAngleBotAction(state);

            AssertDirection(state, Dir.Left);

            SimulateSpinning(state, action);

            AssertEnd(state);
        }

        [Test]
        public void Can_Go_From_20_To_20()
        {
            var state = new GoToAngleTestState { CurrentAngle = 20, TargetAngle = 20 };
            var action = PrepareGoToAngleBotAction(state);

            AssertDirection(state, Dir.Stop);
            AssertEnd(state);
        }







        #region Common
        private static void AssertEnd(GoToAngleTestState state)
        {
            Assert.That(state.HasEnded);
            Assert.That(MathUtil.AnglesDiff(state.CurrentAngle, state.TargetAngle), Is.LessThanOrEqualTo(20));
        }
        private static void AssertDirection(GoToAngleTestState state, Dir dir)
        {
            Assert.That(state.Direction, Is.EqualTo(dir), "Rotation direction was not as expected");
        }
        private static void SimulateSpinning(GoToAngleTestState state, GoToAngleBotAction action)
        {
            var time = DateTime.Now;
            var count = 0;
            while (!state.HasEnded && count++ < 100)
            {
                //Console.WriteLine("currentAngle: {0} dir:{1}", state.CurrentAngle, state.Direction);

                action.OnCompassChanged(state.CurrentAngle, time.AddMilliseconds(count * 20));

                var coeff = state.Direction == Dir.Left ? -1 : 1;
                state.CurrentAngle += (MathUtil.RandomIntFromRange(10, 20) * coeff);
                if (state.CurrentAngle < 0) state.CurrentAngle += 360;
                if (state.CurrentAngle > 360) state.CurrentAngle -= 360;
            }
            Assert.That(count, Is.LessThan(100), "Detected endless loop");
        }

        private static GoToAngleBotAction PrepareGoToAngleBotAction(GoToAngleTestState state)
        {
            var action =
                new GoToAngleBotAction
                {
                    Angle = state.TargetAngle,
                    Workflow = GetMock<ILegoBotPrimaWorkflow>(
                                m =>
                                {
                                    m.SetupGet(w => w.RotationAngle).Returns(state.CurrentAngle);
                                    m.Setup(w => w.GoRightSlow()).Callback(() => state.Direction = Dir.Right);
                                    m.Setup(w => w.GoLeftSlow()).Callback(() => state.Direction = Dir.Left);
                                    m.Setup(w => w.SetAction(It.IsAny<IBotAction>())).Callback<IBotAction>(a => state.NextAction = a);
                                    m.Setup(w => w.EndAction(It.IsAny<GoToAngleBotAction>())).Callback(() => state.HasEnded = true);
                                    m.Setup(w => w.Stop()).Callback(() => state.Direction = Dir.Stop);
                                }
                            )
                };
            action.MarkStart();
            action.Initialize();
            return action;
        }

        

        class GoToAngleTestState
        {
            public double TargetAngle;
            public double CurrentAngle;
            public Dir Direction = Dir.Undefined;
            public bool HasEnded;
            public IBotAction NextAction;
        }
        enum Dir
        {
            Undefined,
            Left,
            Right,
            Stop
        }
        #endregion
    }
}